//
// Created by benxb on 2021/12/12.
//

#ifndef BENXDB_LINEAR_INDEX_SEGMENT_H
#define BENXDB_LINEAR_INDEX_SEGMENT_H

#include <fstream>
#include <string>
#include <memory>
#include <common/config.h>
#include <index/linear_index_page.h>

namespace benxdb {

/**
 * Index segment file format:
 *
 *  Header format
 *  ------------------------------------------------------------------------------------------------
 *  | SegmentId (8) | NextSegmentId (8) | KeyType(4) | MinKey(8) | MaxKey(8) | IndexPageCount (4) |
 *  -----------------------------------------------------------------------------------------------
 *  ------------------------------------------------------------------------------------
 *  | TempPageOffset(4) | TempPageSize(4) | IndexPage1Offset (4) | IndexPage1Size (4) |
 *  ------------------------------------------------------------------------------------
 *  Segment file format
 *  --------------------------------------------------------------------------------
 *  | Header (16) | IndexPage1Data () | IndexPage2Data () | ... | TempPageData () |
 *  -------------------------------------------------------------------------------
 *  In above format, IndexPage1, IndexPage2 are compressed, TempPage is not compressed
 *  When appending new index, it would be first inserted into temp page, when temp page is full,
 *  it will compress and append into file
 *
 */

struct LinearIndexOffsetSize {
    index_page_offset_t offset;
    index_page_size_t size;
};

struct LinearIndexSegmentHeader {
    index_seg_id_t seg_id = 0;
    index_seg_id_t next_seg_id = 0;
    LinearIndexKeyType key_type = STYPE_FLOAT;
    LinearIndexKey min_key;
    LinearIndexKey max_key;
    uint32_t page_count = 0;
    LinearIndexOffsetSize tmp_page{0, 0};
    LinearIndexOffsetSize page_ids[MAX_PAGES_PER_SEGMENT];
};

class LinearIndexSegment {
public:
    /**
     * Construct index segment
     * @param index_dir index directory
     * @param seg_id segment id == file name
     * @param type index key type
     */
    LinearIndexSegment(const std::string index_dir, index_seg_id_t seg_id, LinearIndexKeyType type);
    ~LinearIndexSegment();

    /**
     * Append index leaf pair into tmp file
     * @param pair
     */
    void AppendIndexPair(LinearIndexLeafPair& pair);

    /**
     * Append many index pairs
     * @param pairs index leaf pairs
     */
    void AppendManyIndexPairs(std::vector<LinearIndexLeafPair>& pairs);

    /**
     * Append many index pairs
     * @param pairs index leaf pairs
     * @param cposL start index
     * @param cposH end index
     */
    void AppendManyIndexPairs(std::vector<LinearIndexLeafPair>& pairs, int cposL, int cposH);

    /**
     * Append single index but not flush into disk
     * @param pair index pair
     */
    void AppendSingleIndexWithoutFlush(LinearIndexLeafPair &pair);

    /**
     * Write header back to segment file
     */
    void WriteHeader();

    void SetSegmentId(index_seg_id_t seg_id);
    index_seg_id_t GetSegmentId() const;

    void SetNextSegmentId(index_seg_id_t next_seg);
    index_seg_id_t GetNextSegmentId() const;

    void SetPageCount(uint32_t page_count);
    uint32_t GetPageCount() const;

    LinearIndexKey GetMinKey() const {
        return header_.min_key;
    }

    LinearIndexKey GetMaxKey() const {
        return header_.max_key;
    }

    /**
     * Get index page of curtain index
     * @param page_index page index
     * @param page read page
     * @return
     */
    bool GetIndexPage(uint32_t page_index, LinearIndexPage& page);

    /**
     * Create a temp segment
     * @param tmp_index temp index for this index segment
     * @return temp index
     */
    std::shared_ptr<LinearIndexSegment> CreateTempSegment(int tmp_index);

    /**
     * Get middle index key, approximately
     * @return mid key
     */
    LinearIndexKey GetApproximateMid();

    /**
     * Flush index_io into disk
     */
    void Flush();

    /**
     * Delete this index segment file
     */
    void Destroy();

    /**
     * Rename this segment file and reload file
     * @param seg_id new segment id
     */
    void RenameSegment(index_seg_id_t seg_id);

    // Create temp index segment
    LinearIndexSegment(const std::string tmp_index_file, LinearIndexKeyType type);

private:
    static constexpr int OFFSET_SEGMENT_ID = 0;
    static constexpr int OFFSET_NEXT_SEGMENT_ID = OFFSET_SEGMENT_ID + sizeof(index_seg_id_t);
    static constexpr int OFFSET_PAGE_COUNT = OFFSET_NEXT_SEGMENT_ID + sizeof(index_seg_id_t);
    static constexpr int OFFSET_INDEX_BEGIN = sizeof(LinearIndexSegmentHeader);
    static constexpr int HEADER_SIZE = OFFSET_INDEX_BEGIN;

    // index file directory
    std::string index_dir_;
    // index segment file name
    std::string index_file_;
    // index segment file io
    std::fstream index_io_;
    // index segment file size
    int index_file_size_{};
    // key type
    LinearIndexKeyType key_type_;
    // index segment header
    LinearIndexSegmentHeader header_;
    // read header from segment file
    void ReaderHeader();
    // compress temp index page
    void CompressTmpPage();

    // read segment file
    void ReadSegmentFile();
    // write segment file back
    void WriteTmpPageHeader();

    /**
     * Init segment header
     * @param id segment id
     * @param type key type
     */
    void InitHeader(index_seg_id_t id, LinearIndexKeyType type);
};

}


#endif //BENXDB_INDEX_SEGMENT_H
